import re
import json
import logging
from django import http
from django.shortcuts import render, redirect
from django.http import HttpResponse, HttpRequest, HttpResponseForbidden, JsonResponse
from django.views import View
from django.db.utils import DatabaseError
from django.urls import reverse
from django.contrib.auth import login, logout
from django_redis import get_redis_connection
from django.contrib.auth import authenticate
from django.contrib.auth.mixins import LoginRequiredMixin

from . import constants
from .models import User, Address
from goods.models import SKU
from areas.models import Area
from meiduo_mall.utils.response_code import RETCODE, err_msg
from meiduo_mall.utils.views import LoginRequiredJSONMixin
from celery_tasks.email.tasks import send_email_verify
from .utils import generate_email_verify_url, check_email_verify_token
from carts.utils import union_carts

# Create your views here.

logger = logging.getLogger('django')


class UserBrowseHistoryView(LoginRequiredJSONMixin, View):
    """用户浏览记录视图"""
    def get(self, request):
        """获取用户浏览记录"""
        # 获取用戶id
        user_id = request.user.id
        # 创建ｒｅｄｉｓ链接
        redis_conn = get_redis_connection('history')
        # 获取记录，构造上下文
        """
            {
                "code":"0",
                "errmsg":"OK",
                "skus":[
                    {
                        "id":6,
                        "name":"Apple iPhone 8 Plus (A1864) 256GB 深空灰色 移动联通电信4G手机",
                        "default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRbI2ARekNAAFZsBqChgk3141998",
                        "price":"7988.00"
                    },
                    ......
                ]
            }
        """
        try:
            history_skus = [
                {
                    'id': sku_id.decode(),
                    'name': SKU.objects.get(id=sku_id.decode()).name,
                    'default_image_url': SKU.objects.get(id=sku_id.decode()).default_image.url,
                    'price': SKU.objects.get(id=sku_id.decode()).price
                }
                for sku_id in redis_conn.lrange('history_{}'.format(user_id), 0, -1)
            ]
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '查询失败'})

        # 返回相应
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'skus': history_skus})

    def post(self, request):
        """记录用户浏览记录方法"""
        # 获取参数和校验参数
        sku_id = json.loads(request.body.decode()).get('sku_id')
        try:
            category = SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist:
            return http.HttpResponseForbidden('不存在的商品')

        # 创建Redis链接
        redis_conn = get_redis_connection('history')
        # 创建redis管道
        pl = redis_conn.pipeline()
        # 获取用户id
        user_id = request.user.id
        # 去重
        pl.lrem('history_{}'.format(user_id), 0, sku_id)  # count=0　表示移除所有sku_id
        # 加入
        pl.lpush('history_{}'.format(user_id), sku_id)
        # 截取
        pl.ltrim('history_{}'.format(user_id), 0, 4)
        # 執行命令
        pl.execute()

        # 返回响应
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK'})


class ChangePasswordView(LoginRequiredMixin, View):
    """修改密码视图"""
    def get(self, request):
        """获取修改密码页面"""
        return render(request, 'user_center_pass.html')
        pass

    def post(self, request):
        """修改密码请求"""
        # 获取参数
        old_password = request.POST.get('old_password')
        new_password = request.POST.get('new_password')
        new_password2 = request.POST.get('new_password2')

        # 校验参数
        if not all([old_password, new_password, new_password2]):
            return http.HttpResponseForbidden('缺少参数')

        # 校验原始密码
        if not request.user.check_password(old_password):
            return render(request, 'user_center_pass.html', context={'origin_password_errmsg': '密码输入有误'})

        # 校验两次新密码
        if (not re.match(r'^[a-zA-Z0-9_-]{8,20}$', new_password)) or (new_password2 != new_password):  # 校验密码
            return http.HttpResponseForbidden('非法访问')

        # 设置新密码
        try:
            request.user.set_password(new_password)
            request.user.save()
        except Exception as e:
            logger.error(e)
            return render(request, 'user_center_pass.html', context={'change_password_errmsg': '修改密码失败'})

        # 退出登录
        logout(request)

        # 设置响应
        response = redirect(to=reverse('users:login'))

        # 删除cookie
        response.delete_cookie('username')

        # 返回响应
        return response


class ChangeTitleView(LoginRequiredJSONMixin, View):
    """修改标题视图"""
    def put(self, request, address_id):
        # 获取参数
        title = json.loads(request.body.decode()).get('title')

        # 校验参数
        if not title:
            return http.HttpResponseForbidden('缺少参数')

        # 修改title
        try:
            address = Address.objects.get(id=address_id)
            address.title = title
            address.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '更新失败'})

        # 返回响应
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '更新成功'})


class SetDefaultAddressView(LoginRequiredJSONMixin, View):
    """设置默认收货地址视图"""
    def put(self, request, address_id):
        # 判断该地址是否存在
        if not request.user.addresses.get(id=address_id):
            # 非法请求
            return http.HttpResponseForbidden('非法请求')

        # 更改用户的默认地址
        request.user.default_address_id = address_id
        request.user.save()

        # 返回响应
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK'})


class UpdateDestroyView(LoginRequiredJSONMixin, View):
    """更新和删除地址视图"""
    def put(self, request, address_id):
        """更新地址方法"""
        # 获取参数
        data = json.loads(request.body.decode())
        receiver = data.get('receiver')
        province_id = data.get('province_id')
        city_id = data.get('city_id')
        district_id = data.get('district_id')
        place = data.get('place')
        mobile = data.get('mobile')
        tel = data.get('tel')
        email = data.get('email')

        # 校验参数
        if not all([receiver, province_id, city_id, district_id, place, mobile]):
            return http.HttpResponseForbidden('缺少参数')
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return http.HttpResponseForbidden('参数mobile有误')
        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return http.HttpResponseForbidden('参数tel有误')
        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return http.HttpResponseForbidden('参数email有误')

        # 更行数据库中对应的数据
        try:
            # model1 = Address.objects.get(id=address_id)  # get是获取Address对象,用这种方法也可以改，需要用model1.pro1 = ,model1.pro2=, ... , model1.save()
            # model2 = Address.objects.filter(id=address_id)  # filter是获取QuerySet
            # 更新操作，要先filter，再update
            Address.objects.filter(id=address_id).update(  # 会返回受更新的数据的条数
                receiver=receiver,
                province_id=province_id,
                city_id=city_id,
                district_id=district_id,
                place=place,
                mobile=mobile,
                tel=tel,
                email=email
            )
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '更新失败'})

        # 构建响应结果
        address_model = Address.objects.get(id=address_id)
        address_dict = {
            "id": address_model.id,
            "title": address_model.title,
            "receiver": address_model.receiver,
            "province": address_model.province.name,
            "city": address_model.city.name,
            "district": address_model.district.name,
            "place": address_model.place,
            "mobile": address_model.mobile,
            "tel": address_model.tel,
            "email": address_model.email
        }

        # 返回响应
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'address': address_dict})

    def delete(self, request, address_id):
        """删除地址请求"""
        try:
            address = Address.objects.get(id=address_id)
            address.is_deleted = True
            address.save()

            if int(request.user.default_address_id) == int(address_id):
                # 删除默认地址
                request.user.default_address = None
                request.user.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '删除失败'})

        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '删除成功'})


class AddressCreateView(LoginRequiredJSONMixin, View):
    """新增收货地址视图"""
    def post(self, request):
        # 获取当前用户拥有的收货地址数量
        try:
            count = request.user.addresses.count()
            if count > constants.USER_ADDRESS_COUNTS_LIMIT:  # 注册地址超过最大限制
                return http.JsonResponse({'code': RETCODE.THROTTLINGERR, 'errmsg': '注册地址超过最大限制'})
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '数据库查询错误'})

        # 接收参数
        request_data = json.loads(request.body.decode())
        receiver = request_data.get('receiver')
        province_id = request_data.get('province_id')
        city_id = request_data.get('city_id')
        district_id = request_data.get('district_id')
        place = request_data.get('place')
        mobile = request_data.get('mobile')
        tel = request_data.get('tel')
        email = request_data.get('email')

        # 校验参数
        if not all([receiver, province_id, city_id, district_id, place, mobile]):
            return http.HttpResponseForbidden('缺少参数')
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return http.HttpResponseForbidden('参数mobile有误')
        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return http.HttpResponseForbidden('参数tel有误')
        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return http.HttpResponseForbidden('参数email有误')

        # 将地址存入数据库
        try:
            address = Address.objects.create(
                user=request.user,
                title=receiver,  # 根据产品要求再起名字，这里默认接收人
                receiver=receiver,
                # province=Area.objects.get(id=province_id),  如果直接使用定义的外键的字段名，那么必须赋值为对象
                # city=Area.objects.get(id=city_id),
                # district=Area.objects.get(id=district_id),
                province_id=province_id,  # 如果要赋值id，那么用外键_id
                city_id=city_id,
                district_id=district_id,
                place=place,
                mobile=mobile,
                tel=tel,
                email=email
            )

            # 默认地址
            if not request.user.default_address:  # 没有默认地址，将改地址设置为默认地址
                request.user.default_address = address
                request.user.save()  # 要调用save方法才能跟新数据库
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '存储失败'})

        # 返回响应
        address_dict = {
            "id": address.id,
            "title": address.title,
            "receiver": address.receiver,
            "province": address.province.name,
            "city": address.city.name,
            "district": address.district.name,
            "place": address.place,
            "mobile": address.mobile,
            "tel": address.tel,
            "email": address.email
        }
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'address': address_dict})


class AddressView(LoginRequiredMixin, View):
    """获取收货地址视图"""
    def get(self, request):
        # 查询数据
        try:
            # 两种方法都可以
            # address_model_list = Address.objects.filter(user=request.user, is_deleted=False)
            address_model_list = request.user.addresses.filter(is_deleted=False)

            address_list = [{
                'id': address_model.id,
                'title': address_model.title,
                'receiver': address_model.receiver,
                'province': address_model.province.name,
                'city': address_model.city.name,
                'district': address_model.district.name,  # js是不认识python模型类对象，如果将对象传到前端，前端js不能将对象赋值给vue
                'place': address_model.place,
                'mobile': address_model.mobile,
                'tel': address_model.tel,
                'email': address_model.email
            } for address_model in address_model_list]

            if not request.user.default_address_id:
                default_address_id = 0
            else:
                default_address_id = request.user.default_address_id

            # 构造数据
            # 方法一
            # address_list_json = json.dumps(address_list, ensure_ascii=False)
            # context = {
            #     'addresses': address_list_json,
            #     'default_address_id': default_address_id
            # }
            # 方法二
            context = {  # 两种方法都可以，不过迁移中方法多加了json.dumps方法，后续前端和后端代码不变
                'addresses': address_list,
                'default_address_id': default_address_id
            }

            # 返回结果
            return render(request, 'user_center_site.html', context=context)

        except Exception as e:
            logger.error(e)
            return render(request, 'user_center_site.html')


class EmailVerifyView(View):
    """邮箱验证视图"""

    def get(self, request):
        # 获取参数
        token = request.GET.get('token')

        # 校验参数
        if not token:
            return http.HttpResponseForbidden('缺少token')

        # 参数存在，对token进行反序列化
        user = check_email_verify_token(token)
        if not user:
            return http.HttpResponseForbidden('无效的token')

        # 将数据库中user的email_active字段设置为true
        try:
            user.email_active = True
            user.save()
        except Exception as e:
            logger.error(e)
            return http.HttpResponseServerError('邮箱激活失败')

        # 返回响应
        return redirect(to=reverse('users:info'))


class EmailView(LoginRequiredJSONMixin, View):
    """添加邮箱视图"""

    def put(self, request):  # put请求用于更新数据
        # 判断用户是否登录，方法一，不用继承任何类
        # if not request.user.is_authenticated:  # 用户未登录
        #     return http.JsonResponse({'code': RETCODE.SESSIONERR, 'errmsg': '用户未登录'})

        # 判断用户是否登录，方法二，因为LoginRequiredMixin当用户未登录时返回的时redirect，与预期的Json不符，所以要重写其中的handle_no_permission方法

        # 获取邮箱
        json_str = request.body.decode()
        email = json.loads(json_str).get('email')

        # 校验邮箱
        if not email:
            return http.HttpResponseForbidden('缺少email参数')
        if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
            return http.HttpResponseForbidden('参数email有误')

        # 将邮箱存储到数据库中
        try:
            request.user.email = email
            request.user.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '保存失败'})

        # 给用户发送邮箱验证
        # 生成验证链接
        verify_url = generate_email_verify_url(request.user)
        send_email_verify.delay(to_email=email, verify_url=verify_url)  # 使用celery，即消费者

        # 返回响应
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK'})


class UserInfoView(LoginRequiredMixin, View):
    """用户中心视图"""

    def get(self, request):
        """获取用户中心页面"""
        # 方法一：
        # # 判断用户是否登录，如果登录返回个人中心页面，没有登录就返回登录页面
        # if request.user.is_authenticated:  # 用户已登录, django再带的用户登录认证方法
        #     return render(request, 'user_center_info.html')
        # else:  # 用户未登录
        #     return redirect(to=reverse('users:login'))

        # 方法二：
        # 使用LoginRequiredMixin进行用户验证
        # login_url = '/login/'  # 父类中login_url = self.login_url or settings.LOGIN_URL，所以在配置文件中默认即可
        # 获取cookie中是否存在用户名，存在就重定向到用户中心页面
        # if request.COOKIES.get('username'):  # 先执行父类中的方法，如果没有登录直接重定向向到LOGIN_URL了，不会执行后面的
        context = {
            'username': request.user.username,  # 使用django自带的LoginRequiredMixin判断用户是否登录后，如果登录，那么request.user就是User对象
            'mobile': request.user.mobile,
            'email': request.user.email,
            'email_active': request.user.email_active
        }

        return render(request, 'user_center_info.html', context=context)


class LogoutView(View):
    """登出视图"""

    def get(self, request):
        # 清除django认证系统自带的cookie和session
        logout(request)

        # 创建重定向响应
        response = redirect(to=reverse('contents:index'))

        # 清除用户cookie信息
        response.delete_cookie('username')

        # 返回响应
        return response


class LoginView(View):
    """用户登录视图"""

    def get(self, request):
        """加载用户登录页面"""
        return render(request, 'login.html')

    def post(self, request):
        # 接收参数
        account = request.POST.get('username')
        password = request.POST.get('password')
        remembered = request.POST.get('remembered')
        next_url = request.GET.get('next')

        # 校验参数
        if not all([account, password]):
            # 缺少参数
            return HttpResponseForbidden('缺少参数')
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', account):  # 校验用户名
            return HttpResponseForbidden('用户名错误', password)
        if not re.match(r'^[a-zA-Z0-9_-]{8,20}$', password):  # 校验密码
            return HttpResponseForbidden('密码错误')

        # 实现业务逻辑
        # 用户认证
        user = authenticate(request=request, username=account, password=password)
        if not user:  # 用户不存在
            return render(request, 'login.html', context={'login_error': '用户名或者密码错误'})
        # 状态保持
        login(request, user)
        # 设置session过期时间
        if remembered != 'on':  # 会话结束就删除
            request.session.set_expiry(0)
        else:  # 两周后结束
            request.session.set_expiry(None)

        # 返回结果
        # 判断重定向页面
        if next_url:  # 指定重定向页面
            response = redirect(next_url)
        else:  # 重定向到默认页
            response = redirect(to=reverse('contents:index'))  # reverse解析的路由不包括域名和端口，只是资源路径，即路由
        response.set_cookie('username', user.username, max_age=3600 * 24 * 15)  # 将用户名添加到cookie
        # 合并购物车
        response = union_carts(request, user, response)

        return response


class UserNameCountView(View):
    """校验用户名是否重复视图"""

    def get(self, request, username):
        """校验用户名是否注册的get方法"""
        # 时间核心业务逻辑：从数据库中查询用户的数量(filter方法返回的是查询结果集)
        count = User.objects.filter(username=username).count()
        # print('hello')

        # 返回响应
        return JsonResponse({'code': RETCODE.OK, 'errmsg': err_msg[RETCODE.OK], 'count': count})


class MobileCountView(View):
    """校验手机号码是否重复视图"""

    def get(self, request, mobile):
        """校验手机号码是否重复的get方法"""
        # 实现核心业务逻辑：查询该号码在是数据库中的数量
        count = User.objects.filter(mobile=mobile).count()

        # 返回响应
        return JsonResponse({'code': RETCODE.OK, 'errmsg': err_msg[RETCODE.OK], 'count': count})


class RegisterView(View):
    def get(self, request):

        return render(request, 'register.html')
        pass

    def post(self, request):
        """提交注册数据"""
        # 获取参数
        username = request.POST.get('username')
        password = request.POST.get('password')
        password2 = request.POST.get('password2')
        mobile = request.POST.get('mobile')
        check_on = request.POST.get('allow')
        sms_code = request.POST.get('sms_code')

        # 校验参数
        if not all([username, password, password2, mobile, check_on]):  # 判断参数是否齐全
            return HttpResponseForbidden('缺少必传参数')
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', username):  # 校验用户名
            return HttpResponseForbidden('用户名错误', password)
        if not re.match(r'^[a-zA-Z0-9_-]{8,20}$', password):  # 校验密码
            return HttpResponseForbidden('密码错误')
        if password != password2:  # 确认密码
            return HttpResponseForbidden('两次输入的密码不一样')
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return HttpResponseForbidden('手机号码错误')
        if check_on != 'on':
            return HttpResponseForbidden('未勾选用户协议')
        # 校验短信验证码
        # 从redis中获取该页面的短信验证码
        redis_conn = get_redis_connection('verify_code')
        redis_sms_code = redis_conn.get('sms_code_' + mobile)
        redis_conn.delete('sms_code_' + mobile)  # 删除短信验证码，防止恶意用户测试
        if redis_sms_code == None:  # 对于响应方式，有两个突破口：1。开发文档，文档要求用render 2。根据提交方式，表单一般都用render
            return render(request, 'register.html', {'sms_code_errmsg': '无效的短信验证码'})
        if redis_sms_code.decode() != sms_code:
            return render(request, 'register.html', {'sms_code_errmsg': '输入短信验证码有误'})

        # return render(request, 'register.html', context={'register_error': '注册失败'})

        # 注册用户
        try:
            user = User.objects.create_user(username, password=password, mobile=mobile)
        except DatabaseError as e:
            return render(request, 'register.html', context={'register_error': '注册失败'})

        # 用户注册成功，实现状态保持
        login(request, user)

        # 返回主页面
        # reverse('content:index') == '/'
        response = redirect(to=reverse('contents:index'))
        response.set_cookie('username', username, max_age=3600 * 24 * 15)
        return response
